package com.ppd.generator;

import com.ppd.generator.wrapper.ClassInfo;
import com.ppd.generator.wrapper.DBTableInfo;
import com.ppd.generator.wrapper.GenerateUtils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.JdbcType;
import org.apache.log4j.Logger;

import java.io.*;
import java.sql.*;
import java.util.*;


/**
 * @author abel.lin
 * @date_time 2017/2/24 10:40
 * @description
 */
public class App {
    private final Logger log = Logger.getLogger(App.class);
    private boolean importUtil = false; // 是否需要导入包java.util.Date
    private boolean importMath = false; // 是否需要导入包java.math.*
    private static String rootPath = "";
    private static String tplPath = "";
    private static String prefix = "";   //忽略数据表前缀
    private static String suffix = "";   //忽略数据表后缀


    public App(){
        //创建连接
        Connection con = null;

        PreparedStatement pStemt = null;
        try {
            rootPath = rootPath.replaceAll("\\\\","/");
            log.info("rootPath="+rootPath);

            ResourceBundle rb = getResourceBundle();
            if(rb == null){
                log.info("config.properties load failed");
                return;
            }

            tplPath = rb.containsKey("template.path")?rb.getString("template.path").trim():"";
            tplPath = rootPath + tplPath;
            log.info("tplPath="+tplPath);

            //数据库连接
            String DRIVER = rb.containsKey("jdbc.driver")?rb.getString("jdbc.driver").trim():"";
            String URL = rb.containsKey("jdbc.url")?rb.getString("jdbc.url").trim():"";
            String NAME = rb.containsKey("jdbc.username")?rb.getString("jdbc.username").trim():"";
            String PASS = rb.containsKey("jdbc.password")?rb.getString("jdbc.password").trim():"";
            String tableNames = rb.containsKey("jdbc.tables")?rb.getString("jdbc.tables"):"";

            String[] tableNameArr = tableNames.split(",");
            if(StringUtils.isBlank(DRIVER) || StringUtils.isBlank(URL) || StringUtils.isBlank(NAME) || tableNameArr.length == 0){
                return;
            }
            try {
                Class.forName(DRIVER);
            } catch (ClassNotFoundException e1) {
                e1.printStackTrace();
            }
            con = DriverManager.getConnection(URL,NAME,PASS);
            log.info("===connection success===");

            String dataBase = rb.containsKey("jdbc.database")?rb.getString("jdbc.database"):"";
            boolean genBase = rb.containsKey("gen.type.basetable")?rb.getString("gen.type.basetable").trim().equals("true"):false;
            boolean genService = rb.containsKey("gen.type.service")?rb.getString("gen.type.service").trim().equals("true"):false;
            boolean genDomain = rb.containsKey("gen.type.domain")?rb.getString("gen.type.domain").trim().equals("true"):false;
            boolean genController = rb.containsKey("gen.type.controller")?rb.getString("gen.type.controller").trim().equals("true"):false;
            boolean genBaseDao = rb.containsKey("base.daoCreate")?rb.getString("base.daoCreate").trim().equals("true"):false;
            prefix = rb.containsKey("prefix.tableName")?rb.getString("prefix.tableName"):"";
            suffix = rb.containsKey("suffix.tableName")?rb.getString("suffix.tableName"):"";
            for(int i=0; i<tableNameArr.length; i++){
                String tableName = tableNameArr[i].trim();

                importUtil = false;
                importMath = false;
                log.info("===generate table "+tableName+" begin===");
                if(genBase) {
                    List<DBTableInfo> tableList = getTableInfo(con, pStemt, dataBase, tableName);
                    generate(rb, GenerateUtils.GenType.MODEL.getCode(), tableName, tableList);
                }
                if(genService){
                    generate(rb,GenerateUtils.GenType.SERVICE.getCode(), tableName, null);
                }
                if(genDomain){
                    generate(rb,GenerateUtils.GenType.DOMAIN.getCode(), tableName, null);
                }
                if(genController){
                    generate(rb,GenerateUtils.GenType.CONTROLLER.getCode(), tableName, null);
                }
                if(genBaseDao){
                    genBaseMapper(rb);
                }
                log.info("===generate table "+tableName+" end===");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally{
            try {
                if(con != null){
                    con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    private ResourceBundle getResourceBundle(){
        ResourceBundle rb = null;
        try {
            String proFilePath = rootPath+"/conf/config.properties";
            BufferedInputStream  inputStream = new BufferedInputStream(new FileInputStream(proFilePath));
            rb = new PropertyResourceBundle(inputStream);
            inputStream.close();
            return rb;
        } catch (FileNotFoundException e) {
            log.info("load config");
            rb = ResourceBundle.getBundle("config");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return rb;
    }

    public List<DBTableInfo> getTableInfo(Connection con, PreparedStatement pStemt, String dataBase, String tableName) throws SQLException {
        String sql = "select * from " + tableName;
        List<DBTableInfo> list = new ArrayList<DBTableInfo>();

        String primaryKey = getKeyName(con, dataBase, tableName);
        pStemt = con.prepareStatement(sql);
        ResultSetMetaData rsmd = pStemt.getMetaData();
        int size = rsmd.getColumnCount();   //统计列
        for (int i = 0; i < size; i++) {
            DBTableInfo table = new DBTableInfo();
            table.setColname(rsmd.getColumnName(i + 1));
            table.setColType(rsmd.getColumnTypeName(i + 1));
            table.setLowerColname(GenerateUtils.underlineToUpper(table.getColname().toLowerCase(),"_",""));
            table.setUpperColname(GenerateUtils.firstCharToUpper(table.getLowerColname()));
            table.setJavaType(GenerateUtils.sqlType2JavaType(table.getColType()));
            table.setJdbcType(JdbcType.forCode(rsmd.getColumnType(i+1)).toString());
            table.setPrimary(table.getColname().equals(primaryKey));

            if(table.getColType().equalsIgnoreCase("datetime") || table.getColType().equalsIgnoreCase("date")
                    ||table.getColType().equalsIgnoreCase("timestamp")){
                importUtil = true;
            }
            if(table.getColType().equalsIgnoreCase("decimal")){
                importMath = true;
            }
            list.add(table);
        }

        return list;
    }


    private void genBaseMapper(ResourceBundle rb){
        String javaPath = rb.containsKey("java.path")?rb.getString("java.path").trim():"";
        if(StringUtils.isBlank(javaPath)){
            javaPath = rootPath;
        }
        javaPath = javaPath.replaceAll("\\\\","/");
        if(!javaPath.endsWith("/")){
            javaPath += "/";
        }
        String mapperPackage = rb.containsKey("base.package")?rb.getString("base.package").trim():"";
        String baseMapper = rb.containsKey("base.entity")?rb.getString("base.entity").trim():"";

        String mapperPath = javaPath+mapperPackage.replace(".", "/")+"/";
        //生成mapper的java类
        String mapperFile = baseMapper + ".java";

        ClassInfo info = new ClassInfo();
        info.setBaseMapperPackage(mapperPackage);
        info.setBaseMapper(baseMapper);
        String content = constructBaseMapper(info);
        GenerateUtils.geneFile(content,mapperFile,mapperPath);
        log.info("===generate Base Mapper complete===");
    }

    public void generate(ResourceBundle rb, String type, String tableName, List<DBTableInfo> tableList){
        if(StringUtils.isBlank(type)) return;

        ClassInfo info = new ClassInfo();

        String content = "";

        String javaPath = rb.containsKey("java.path")?rb.getString("java.path").trim():"";
        if(StringUtils.isBlank(javaPath)){
            javaPath = rootPath;
        }
        javaPath = javaPath.replaceAll("\\\\","/");
        if(!javaPath.endsWith("/")){
            javaPath += "/";
        }

        String xmlPath = rb.containsKey("xml.path")?rb.getString("xml.path").trim():"";
        if(StringUtils.isBlank(xmlPath)){
            xmlPath = rootPath;
        }
        xmlPath = xmlPath.replaceAll("\\\\","/");
        if(!xmlPath.endsWith("/")){
            xmlPath += "/";
        }

        String tableNameEntity = tableName;
        if(StringUtils.isNotBlank(prefix) && tableName.startsWith(prefix)) {
            tableNameEntity = StringUtils.substringAfter(tableName, prefix);
        }
        if(StringUtils.isNotBlank(suffix) && tableNameEntity.endsWith(suffix)) {
            tableNameEntity = StringUtils.substringBefore(tableNameEntity, suffix);
        }

        String tableUpper = GenerateUtils.underlineToUpper(tableNameEntity.toLowerCase(),"_","");
        tableUpper = GenerateUtils.firstCharToUpper(tableUpper);

        String controllerSuffix = rb.containsKey("suffix.controller")?rb.getString("suffix.controller").trim():"Controller";
        String modelSuffix = rb.containsKey("suffix.entity")?rb.getString("suffix.entity").trim():"Model";
        String criteriaSuffix = rb.containsKey("suffix.criteria")?rb.getString("suffix.criteria").trim():"ModelCriteria";
        String mapperSuffix = rb.containsKey("suffix.mapper")?rb.getString("suffix.mapper").trim():"ModelMapper";
        String serviceSuffix = rb.containsKey("suffix.service")?rb.getString("suffix.service").trim():"Service";
        String domainSuffix = rb.containsKey("suffix.domain")?rb.getString("suffix.domain").trim():"Domain";

        info.setControllerName(tableUpper+controllerSuffix);
        info.setEntityName(tableUpper+modelSuffix);
        info.setCriteriaName(tableUpper+criteriaSuffix);
        info.setMapperName(tableUpper+mapperSuffix);
        info.setServiceName(tableUpper+serviceSuffix);
        info.setDomainName(tableUpper+domainSuffix);
        info.setJspName(tableName.toLowerCase().replace("_","-"));
        if(tableName.indexOf("_")>=0){
            info.setTableFirstName(tableName.toLowerCase().substring(0,tableName.indexOf("_")));
        }else {
            info.setTableFirstName(tableName.toLowerCase());
        }

        info.setControllerPackage(rb.containsKey("package.controller")?rb.getString("package.controller").trim():"");
        info.setEntityPackage(rb.containsKey("package.entity")?rb.getString("package.entity").trim():"");
        info.setMapperPackage(rb.containsKey("package.mapper")?rb.getString("package.mapper").trim():"");
        info.setServicePackage(rb.containsKey("package.service")?rb.getString("package.service").trim():"");
        info.setDomainPackage(rb.containsKey("package.domain")?rb.getString("package.domain").trim():"");

        info.setBaseMapper(rb.containsKey("base.entity")?rb.getString("base.entity").trim():"");
        info.setBaseMapperPackage(rb.containsKey("base.package")?rb.getString("base.package").trim():"");

        info.setImportUtil(importUtil);
        info.setImportMath(importMath);

        if(type.equals(GenerateUtils.GenType.MODEL.getCode())){

            String modelPath = javaPath+info.getEntityPackage().replace(".", "/")+"/";
            //生成实体类
            String modeFile = info.getEntityName() + ".java";
            content = constructModel(tableList, info);
            GenerateUtils.geneFile(content,modeFile,modelPath);
            log.info("===generate "+tableName+" entity complete===");

            //生成条件类
            boolean genCriteria = rb.containsKey("gen.type.criteria")?rb.getString("gen.type.criteria").trim().equals("true"):false;
            if(genCriteria) {
                String criteriaFile = info.getCriteriaName() + ".java";
                content = constructCriteria(tableList, info);
                GenerateUtils.geneFile(content, criteriaFile, modelPath);
                log.info("===generate " + tableName + " criteria complete===");
            }

            boolean genMapper = rb.containsKey("gen.type.mapper")?rb.getString("gen.type.mapper").trim().equals("true"):false;
            if(genMapper) {
                String mapperPath = javaPath + info.getMapperPackage().replace(".", "/") + "/";
                //生成mapper的java类
                String mapperFile = info.getMapperName() + ".java";
                content = constructMapper(tableList.get(0), info);
                GenerateUtils.geneFile(content, mapperFile, mapperPath);
                log.info("===generate " + tableName + " mapper.java complete===");

                //生成mybatis映射类的mapper.xml文件
                boolean selectKey = rb.containsKey("need.selectKey") ? rb.getString("need.selectKey").trim().equals("true") : false;
                String mapperXmlPath = xmlPath + info.getMapperPackage().replace(".", "/") + "/";
                String mapperXmlFile = info.getMapperName() + ".xml";
                content = constructMapperXml(tableList, info, tableName, selectKey);
                GenerateUtils.geneFile(content, mapperXmlFile, mapperXmlPath);
                log.info("===generate " + tableName + " mapper.xml complete===");
            }

        }else if(type.equals(GenerateUtils.GenType.SERVICE.getCode())){
            //生成service类
            String servicePath = javaPath+info.getServicePackage().replace(".", "/")+"/";
            String serviceFile = info.getServiceName() + ".java";
            content = constructService(info);
            GenerateUtils.geneFile(content,serviceFile,servicePath);
            log.info("===generate "+tableName+" service complete===");

            //生成service实现类
            String serviceImplPath = javaPath+info.getServiceImplPackage().replace(".", "/")+"/";
            String serviceImplFile = info.getServiceImplName() + ".java";
            content = constructServiceImpl(info);
            GenerateUtils.geneFile(content,serviceImplFile,serviceImplPath);
            log.info("===generate "+tableName+" serviceImpl complete===");
        }else if(type.equals(GenerateUtils.GenType.DOMAIN.getCode())){
            //生成继承实体类的子类
            String domainPath = javaPath+info.getDomainPackage().replace(".", "/")+"/";
            String domainFile = info.getDomainName() + ".java";
            content = constructDomain(info);
            GenerateUtils.geneFile(content,domainFile,domainPath);
            log.info("===generate "+tableName+" domain complete===");
        }else if(type.equals(GenerateUtils.GenType.CONTROLLER.getCode())){
            //生成action类
            String actionPath = javaPath+info.getControllerPackage().replace(".", "/")+"/";
            String actionFile = info.getControllerName() + ".java";
            content = constructController(tableList, info);
            GenerateUtils.geneFile(content,actionFile,actionPath);
            log.info("===generate "+tableName+" controller complete===");
        }
    }

    /**
     * 获取数据库表主键
     * @param con
     * @return
     */
    private String getKeyName(Connection con, String dataBase, String tableName){
        String idName = "";
        try {
            DatabaseMetaData metadata = con.getMetaData();
            ResultSet rs = metadata.getPrimaryKeys(dataBase, null, tableName);
            while (rs.next()) {
                idName = rs.getString(4);
            }
            rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return idName;
    }

    /**
     * 生成action类xxxController.java
     * @return
     */
    private String constructController(List<DBTableInfo> tableList,ClassInfo info) {
        String content = "";
        if(info == null || tableList == null || tableList.size() == 0){
            return content;
        }
        if(StringUtils.isBlank(info.getEntityPackage()) || StringUtils.isBlank(info.getEntityName())){
            return content;
        }

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("ControllerTpl.ftl","UTF-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("tableList", tableList);
            root.put("info", info);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 生成实体类xxxModel.java
     * @return
     */
    private String constructModel(List<DBTableInfo> tableList,ClassInfo info) {
        String content = "";
        if(info == null || tableList == null || tableList.size() == 0){
            return content;
        }
        if(StringUtils.isBlank(info.getEntityPackage()) || StringUtils.isBlank(info.getEntityName())){
            return content;
        }

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("ModelTpl.ftl","UTF-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("tableList", tableList);
            root.put("info", info);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 生成条件类xxxCriteria.java
     * @param tableList
     * @return
     */
    private String constructCriteria(List<DBTableInfo> tableList, ClassInfo info){
        String content = "";
        if(info == null || tableList == null || tableList.size() == 0){
            return content;
        }
        if(StringUtils.isBlank(info.getEntityPackage()) || StringUtils.isBlank(info.getEntityName())
                || StringUtils.isBlank(info.getCriteriaName())){
            return content;
        }
        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("CriteriaTpl.ftl","utf-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("tableList", tableList);
            root.put("info", info);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 生成mybatis的映射文件xxxMapper.java
     * @param info
     * @return
     */
    private String constructMapper(DBTableInfo tableKey, ClassInfo info){
        String content = "";

        if(info == null){
            return content;
        }
        if(StringUtils.isBlank(info.getMapperPackage()) || StringUtils.isBlank(info.getMapperName())
                || StringUtils.isBlank(info.getEntityName()) || StringUtils.isBlank(info.getCriteriaName())){
            return content;
        }

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("MapperTpl.ftl","utf-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("info",info);
            root.put("tableKey",tableKey);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 生成mybatis的映射文件xxxMapper.xml
     * @param tableList
     * @param info
     * @param tableName
     * @return
     */
    private String constructMapperXml(List<DBTableInfo> tableList, ClassInfo info, String tableName,boolean selectKey){
        String content = "";

        if(info == null || tableList == null || tableList.size() == 0){
            return content;
        }
        if(StringUtils.isBlank(info.getEntityPackage()) || StringUtils.isBlank(info.getEntityName())
                || StringUtils.isBlank(info.getMapperPackage()) || StringUtils.isBlank(info.getMapperName())){
            return content;
        }

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("MapperXmlTpl.ftl","utf-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("tableList", tableList);
            root.put("info", info);
            root.put("tableName", tableName);
            root.put("tableKey",tableList.get(0));
            root.put("needSelectKey",selectKey);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 生成service类
     * @param info
     * @return
     */
    private String constructService(ClassInfo info){
        String content = "";

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("ServiceTpl.ftl","utf-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("info", info);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 生成service实现类
     * @param info
     * @return
     */
    private String constructServiceImpl(ClassInfo info){
        String content = "";

        if(info == null){
            return content;
        }
        if(StringUtils.isBlank(info.getMapperPackage()) || StringUtils.isBlank(info.getMapperName())
                || StringUtils.isBlank(info.getEntityPackage()) || StringUtils.isBlank(info.getEntityName()) || StringUtils.isBlank(info.getCriteriaName())
                || StringUtils.isBlank(info.getServicePackage()) || StringUtils.isBlank(info.getServiceName())
                || StringUtils.isBlank(info.getServiceImplPackage()) || StringUtils.isBlank(info.getServiceImplName())){
            return content;
        }

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("ServiceImplTpl.ftl","utf-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("info", info);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 生成继承实体类的子类
     * @param info
     * @return
     */
    private String constructDomain(ClassInfo info){
        String content = "";

        if(info == null){
            return content;
        }
        if(StringUtils.isBlank(info.getEntityPackage()) || StringUtils.isBlank(info.getEntityName())
                || StringUtils.isBlank(info.getDomainPackage()) || StringUtils.isBlank(info.getDomainName())){
            return content;
        }

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("DomainTpl.ftl","utf-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("info", info);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    private String constructBaseMapper(ClassInfo info){
        String content = "";

        if(info == null){
            return content;
        }
        if(StringUtils.isBlank(info.getBaseMapperPackage())){
            return content;
        }

        Configuration cfg = new Configuration();
        try {
            cfg.setDirectoryForTemplateLoading(new File(tplPath));
            Template template = cfg.getTemplate("BaseDao.ftl","utf-8");
            StringWriter writer = new StringWriter();
            Map<String, Object> root = new HashMap<String, Object>();
            root.put("info",info);
            template.process(root,writer);
            content = writer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e){
            e.printStackTrace();
        }
        return content;
    }

    public static void main(String[] args) {
        rootPath = System.getProperty("user.dir"); //Thread.currentThread().getContextClassLoader().getResource("").getPath();
        new App();
    }
}
